/*
   Copyright (c) 2011 Mizar Tools Contributors (mizartools.org)

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
/*  Contributors :
 *	2011-02-18 Marco Riccardi - initial implementation
 *
 */
package org.mizartools.dli.utility;

import java.util.LinkedList;

import org.mizartools.dli.And;
import org.mizartools.dli.DecodedLibraryItem;
import org.mizartools.dli.DliException;
import org.mizartools.dli.Ex;
import org.mizartools.dli.For;
import org.mizartools.dli.Formula;
import org.mizartools.dli.Iff;
import org.mizartools.dli.Implies;
import org.mizartools.dli.ItemId;
import org.mizartools.dli.ItemType;
import org.mizartools.dli.Not;
import org.mizartools.dli.Or;
import org.mizartools.dli.Scheme;
import org.mizartools.dli.Thesis;

public class FormulaConverter {

	public static Formula getCompressed(Formula formula) throws DliException{
		return compress(formula, new Changed());
	}

	private static Formula compress(Formula formula, Changed changed) throws DliException{
		Changed changed1 = new Changed();
		Formula formula1 = formula;
		formula1 = insertEx(formula1, changed1);
		formula1 = insertOr(formula1, changed1);
		formula1 = insertImplies(formula1, changed1);
		formula1 = insertIff(formula1, changed1);
		formula1 = deleteNotNot(formula1, changed1);
		if (formula instanceof And) {
			And and = (And)formula;
			Changed changed2 = new Changed();
			boolean compress = false;
			LinkedList<Formula> formulaList = new LinkedList<Formula>();
			for (Formula formula2 : and.getFormulaList()){
				formulaList.add(compress(formula2, changed2));
				if (changed2.isChanged) compress = true;
			}
			if (compress) {
				formula1 = new And(formulaList);
				changed1.isChanged = true;	
			}
		} else if (formula instanceof Or) {
			Or or = (Or)formula;
			Changed changed2 = new Changed();
			boolean compress = false;
			LinkedList<Formula> formulaList = new LinkedList<Formula>();
			for (Formula formula2 : or.getFormulaList()){
				formulaList.add(compress(formula2, changed2));
				if (changed2.isChanged) compress = true;
			}
			if (compress) {
				formula1 = new Or(formulaList);
				changed1.isChanged = true;	
			}
		} else if (formula instanceof For){
			For for1 = (For)formula;
			Changed changed2 = new Changed();
			Formula formula2 = compress(for1.getFormula(), changed2);
			if (changed2.isChanged) {
				formula1 = new For(for1.getVariable(), for1.getType(), formula2);
				changed1.isChanged = true;	
			}
		} else if (formula instanceof Ex){
			Ex ex = (Ex)formula;
			Changed changed2 = new Changed();
			Formula formula2 = compress(ex.getFormula(), changed2);
			if (changed2.isChanged){
				formula1 = new Ex(ex.getVariable(), ex.getType(), formula2);
				changed1.isChanged = true;	
			}
		}
		if (changed1.isChanged){
			formula1 = compress(formula1, new Changed());
			changed.isChanged = true;
		}
		return formula1;
	}
	
	private static Formula insertImplies(Formula formula, Changed changed) throws DliException {
		// $or($not($formula1),$formula2) -> $implies($formula1,$formula2)
		if (formula instanceof Or) {
	    	Or or = (Or)formula;
	    	if (or.getFormulaList().size() == 2){
	    		Formula formula1 = or.getFormulaList().getFirst();
	    		Formula formula2 = or.getFormulaList().getLast();
	    		Formula hypothesis = null;
	    		Formula conclusion = null;
	    		if (formula1 instanceof Not && !(formula2 instanceof Not)){
	    			hypothesis = ((Not)formula1).getFormula();
	    			conclusion = formula2;
	    		}
	    		if (!(formula1 instanceof Not) && formula2 instanceof Not){
	    			hypothesis = ((Not)formula2).getFormula();
	    			conclusion = formula1;
	    		}
	    		if (hypothesis != null && conclusion != null){
		    		changed.isChanged = true;
		    		return new Implies(hypothesis, conclusion);
	    		}
	    	}
		}
		// $not($and($formula1,...,$not($formula_n)) -> $implies($and($formula1,...),$formula_n)
		if (formula instanceof Not) {
	    	Not not = (Not)formula;
	    	Formula formula1 = not.getFormula();
			if (formula1 instanceof And) {
				And and = (And)formula1;
				Formula formula2 = and.getFormulaList().getLast();
				if (formula2 instanceof Not) {
					LinkedList<Formula> formulaList = new LinkedList<Formula>();
					for (Formula formula3 : and.getFormulaList()){
						formulaList.add(formula3);
					}
					formulaList.pollLast();
					Formula hypothesis = null;
					if (formulaList.size() == 1){
						hypothesis = formulaList.getFirst();
					} else {
						hypothesis = new And(formulaList);
					}
					Formula conclusion = ((Not)formula2).getFormula();
		    		changed.isChanged = true;
		    		return new Implies(hypothesis, conclusion);
				}
			}	    	
		}
		return formula;
	}

	private static Formula insertIff(Formula formula, Changed changed) throws DliException {
		// $and($implies($formula1,$formula2),$implies($formula2,$formula1)) -> $iff($formula1,$formula2)
		if (formula instanceof And) {
	    	And and = (And)formula;
	    	if (and.getFormulaList().size() == 2){
	    		Formula formula1 = and.getFormulaList().getFirst();
	    		Formula formula2 = and.getFormulaList().getLast();
	    		if (formula1 instanceof Implies && formula2 instanceof Implies){
	    			Implies implies1 = (Implies)formula1;
	    			Implies implies2 = (Implies)formula2;
	    			if (implies1.getConclusion().toString().equals(implies2.getHypothesis().toString()) && 
	    				implies2.getConclusion().toString().equals(implies1.getHypothesis().toString())	){
	    				Iff iff = new Iff(implies1.getHypothesis(), implies1.getConclusion());
	    				return iff;
	    			}
	    		}
	    	}
		}
		return formula;
	}
	
	private static Formula deleteNotNot(Formula formula, Changed changed) {
		// $not($not($formula)) -> $formula
		if (formula instanceof Not) {
	    	Not not1 = (Not)formula;
	    	Formula formula1 = not1.getFormula();
	    	if (formula1 instanceof Not) {
		    	Not not2 = (Not)formula1;
	    		changed.isChanged = true;
	    		return not2.getFormula();
	    	}
		}
		return formula;
	}

	private static Formula insertOr(Formula formula, Changed changed) throws DliException {
		// $not($and(list_formula)) -> $or(not(list_formula))
		if (formula instanceof Not) {
	    	Not not = (Not)formula;
	    	Formula formula1 = not.getFormula();
	    	if (formula1 instanceof And) {
	    		And and = (And)not.getFormula();
				LinkedList<Formula> formulaList = new LinkedList<Formula>();
				int nrNot = 0;
				for (Formula formula2 : and.getFormulaList()){
					if (formula2 instanceof Not) {
						nrNot++;
						formulaList.add(((Not)formula2).getFormula());
					} else {
						nrNot--;
						formulaList.add(new Not(formula2));
					}
				}
				if (nrNot > 0) {
		    		Or or = new Or(formulaList);
		    		changed.isChanged = true;
		    		return or;
				}
	    	}
		}
		return formula;
	}

	private static Formula insertEx(Formula formula, Changed changed) throws DliException {
		// $not($for($variable,$type,$not($formula))) -> $ex($variable,$type,$formula)
		if (formula instanceof Not) {
	    	Not not = (Not)formula;
	    	Formula formula1 = not.getFormula();
	    	if (formula1 instanceof For) {
		    	For for1 = (For)formula1;
		    	Formula formula2 = for1.getFormula();
		    	if (formula2 instanceof Not) {
		    		Formula formula3 = ((Not)formula2).getFormula();
		    		Ex ex = new Ex(for1.getVariable(), for1.getType(),formula3);
		    		changed.isChanged = true;
					return ex;
		    	}
	    	}
		}
		return formula;
	}
	
	public static DecodedLibraryItem getCompressed(DecodedLibraryItem decodedLibraryItem) throws DliException {
		if (decodedLibraryItem.getItemId().getItemType() == ItemType.sch) {
			ItemId resourceId1 = decodedLibraryItem.getItemId();
			Scheme scheme = (Scheme) decodedLibraryItem.getItemDefinition();
			LinkedList<Formula> formula1List = new LinkedList<Formula>();
			boolean compressed = false;
			for (Formula formula : scheme.getThesis().getFormulaList()){
				Changed changed = new Changed();
				formula1List.add(compress(formula, changed));
				if (changed.isChanged) compressed = true;
			}
			if (compressed) {
				Thesis thesis1 = new Thesis(formula1List);
				Scheme scheme1 = new Scheme(scheme.getName(), scheme.getParameters(), scheme.getPremisses(), thesis1);
				DecodedLibraryItem resource1 = new DecodedLibraryItem(resourceId1, scheme1);
				return resource1;
			} else {
				return decodedLibraryItem;
			}
		}
		return decodedLibraryItem;
	}
	
}
